package link.anycar.study.current.semaphore;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Why use Semaphore if you can do almost the same
 *  with only a ThreadPool?
 * In ThreadPool, every task weights the same,
 *  but when using Semaphore, you can adjust it.
 * Think of an example,
 *  you have 100 jobs,
 *  10 of them are very heavy,
 *  the system burden is very high when more than 2
 *  of that 10 doing at the same time, the system even
 *  stops responding when 4 or 5 such jobs doing at the same time.
 * In a thread pool of, like 5, it would almost absolutely happen
 *  that 5 of such job running at the same time, and the system is not robust.
 * In a semaphore system, with the same 5 permits,
 *  just assign the heavy jobs with 2 permits, the normal job with 1 permit,
 *  you'll never have your system running more than 2 heavy
 *  jobs running together, and you'll at least have 1 permit left
 *  for the system processing simple jobs.
 */
public class Semap {

    static Random rnd = new Random(System.currentTimeMillis());

    public static void main(String[] args) {
        List<Job> jobs = new ArrayList<>(100);
        for (int i=1; i<=100; i++) {
            if (i % 10 == 0) {
                jobs.add(new Job(i, 1000 + rnd.nextInt(500)));
            } else {
                jobs.add(new Job(i, 10 + rnd.nextInt(50)));
            }
        }

        // with semaphore
        Semaphore semaphore = new Semaphore(5);
        ConcurrentLinkedQueue<Job> semJobs = new ConcurrentLinkedQueue<>(jobs);
        AtomicInteger semHeavyJobCount = new AtomicInteger(0);
        AtomicBoolean semSystemRunning = new AtomicBoolean(true);
        for (int i=0; i<5; i++) {
            new Thread(() -> {
                while (!semJobs.isEmpty() && semSystemRunning.get()) {
                    Job job = semJobs.poll();
                    if (job == null) {
                        sleep(1);
                        return;
                    }

                    boolean aquired = semaphore.tryAcquire(job.getPermitsNeeded());
                    if (aquired) {
                        if (job.isHeavyJob()) {
                            int count = semHeavyJobCount.incrementAndGet();
                            if(count > 2) {
                                System.out.println("Semaphore thread ends with too heavy burden, leaving " + semJobs.size() + " jobs aborted.");
                                semSystemRunning.set(false);
                                break; // mocking that the system halt with too heavy burdern
                            }
                        }
                        System.out.println("Sem job " + job.getIndex() + " starts at " + Instant.now());
                        sleep(job.getTimeNeeded());
                        System.out.println("Sem job " + job.getIndex() + " ends at " + Instant.now());
                        if (job.isHeavyJob()) {
                            semHeavyJobCount.decrementAndGet();
                        }
                        semaphore.release(job.getPermitsNeeded());
                        System.out.println();
                    } else {
                        semJobs.offer(job);
                    }
                }
            }).start();
        }

        // without semaphore
        ConcurrentLinkedQueue<Job> noSemJobs = new ConcurrentLinkedQueue<>(jobs);
        AtomicInteger noSemHeavyJobCount = new AtomicInteger(0);
        AtomicBoolean noSemSystemRunning = new AtomicBoolean(true);
        for (int i=0; i<5; i++) {
            new Thread(() -> {
                while (!noSemJobs.isEmpty() && noSemSystemRunning.get()) {
                    Job job = noSemJobs.poll();
                    if (job == null) {
                        sleep(1);
                        return;
                    }
                    if (job.isHeavyJob()) {
                        int count = noSemHeavyJobCount.incrementAndGet();
                        if(count > 2) {
                            noSemSystemRunning.set(false);
                            System.out.println("No semaphore thread ends with too heavy burden, leaving " + noSemJobs.size() + " jobs aborted.");
                            break; // mocking that the system halt with too heavy burdern
                        }
                    }

                    System.out.println("No sem job " + job.getIndex() + " starts at " + Instant.now());
                    sleep(job.getTimeNeeded());
                    System.out.println("No sem job " + job.getIndex() + " ends at " + Instant.now());
                    System.out.println();

                    if (job.isHeavyJob()) {
                        noSemHeavyJobCount.decrementAndGet();
                    }
                }
            }).start();
        }
    }

    static void sleep(long milliSecond) {
        try {
            Thread.sleep(milliSecond);
        } catch (Exception e){}
    }
}
